package api

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"time"

	"gitee.com/kobeyyf/apibox/handler"
	log "gitee.com/kobeyyf/apibox/logger"
	"gitee.com/kobeyyf/apibox/middleware/httpsign"
	_ "gitee.com/kobeyyf/apibox/middleware/httpsign/secret_provider/provider_mem"
	"gitee.com/kobeyyf/apibox/middleware/httptoken"
	"gitee.com/kobeyyf/apibox/middleware/timestamp"
	"github.com/spf13/viper"
	"xorm.io/xorm"
)

const (
	API_MODE_DEBUG = 1 << iota
	API_MODE_TEST
	API_MODE_RELEASE
)

type Api struct {
	handlerPool chan struct{}

	// 基本信息
	AppName string
	AppCode string
	Config  *viper.Viper
	apiMode int

	// 代理
	proxyMap map[string]*url.URL

	// 路由信息
	handles      map[string]handler.HandlerFunc
	handleApi    map[string]*Api
	before       []handler.HandlerFunc
	after        []handler.HandlerFunc
	globalBefore []handler.HandlerFunc
	globalAfter  []handler.HandlerFunc

	// 其他配置
	dbs    map[string]*xorm.Engine
	logger *log.Logger

	sysSignal chan os.Signal
}

func newAPI(config *viper.Viper) (app *Api, err error) {
	app = &Api{
		AppName: config.GetString("app.name"),
		AppCode: config.GetString("app.code"),
		Config:  config,
		dbs:     make(map[string]*xorm.Engine, 2),

		handlerPool: make(chan struct{}, config.GetInt("server.handler_pool")),
	}

	if app.Config.GetBool("static.enabled") {
		if app.Config.GetString("static.pattern") == app.Config.GetString("server.pattern") {
			return nil, fmt.Errorf("static pattern same as api patttern")
		}
	}

	if app.Config.GetBool("apimode.debug") {
		app.apiMode = API_MODE_DEBUG
	} else if app.Config.GetBool("apimode.test") {
		app.apiMode = API_MODE_TEST
	} else {
		app.apiMode = API_MODE_RELEASE
	}

	proxyMap := app.Config.GetStringMapString("proxy")
	app.proxyMap = make(map[string]*url.URL, len(proxyMap))
	for appcode, addrStr := range proxyMap {
		addr, err := url.Parse(addrStr)
		if err != nil {
			log.Warnln(err)
			log.Warnln("ignore proxy", addrStr)
			continue
		}
		app.proxyMap[appcode] = addr
	}

	err = app.initDB()
	if err != nil {
		return nil, err
	}
	err = app.initLogger()
	if err != nil {
		return nil, err
	}

	return app, nil

}

func NewAPI(configPath string) (app *Api, err error) {
	config, err := loadConfigFromFile(configPath)
	if err != nil {
		return nil, err
	}

	return newAPI(config)
}

func NewAipFromConfig(config *viper.Viper) (app *Api, err error) {
	return newAPI(config)
}

func (app *Api) GetLogger() *log.Logger {
	if app.logger != nil {
		return app.logger
	}
	app.initLogger()
	return app.logger
}

func (app *Api) initLogger() error {
	logOut := app.Config.GetString("log.out")

	var out *os.File
	if logOut == "" {
		logOut = "stdout"
	}
	switch logOut {
	case "stdout":
		out = os.Stdout
	case "stderr":
		out = os.Stderr
	default:
		fd, err := os.OpenFile(logOut, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
		if err != nil {
			return err
		}
		out = fd
	}

	flag := log.Lshortfile
	switch app.apiMode {
	case API_MODE_DEBUG:
		flag = flag | log.Ldebug
	case API_MODE_RELEASE:
		// flag = log.Ldate | log.Ltime | log.Lwarn | log.Lshortfile
		flag = log.Ldate | log.Ltime | log.Lwarn
	case API_MODE_TEST:
		flag = flag | log.Linfo
	}

	// app.logger = log.New(out, "", flag)
	log.SetFlags(flag)
	log.SetOutput(out)
	app.logger = log.GetStdLog()
	handler.SetLogger(app.logger)
	return nil
}

// 这里启用的中间件需要在配置中指定
func (app *Api) enableGlobalMiddleware() error {
	// 添加全局的中间件
	if !app.Config.GetBool("apinonce.disabled") {
		app.logger.Debug("todo: apinonce")
	}
	if !app.Config.GetBool("apisign.disabled") {
		authenticator, err := httpsign.NewAuthenticator(app.Config)
		if err != nil {
			return err
		}
		app.enable(authenticator.Authenticated())

		if app.Config.GetBool("apisign.debug") {
			sign_path := app.Config.GetString("apisign.debug_path")
			if sign_path == "" {
				sign_path = "/sign"
			}
			app.logger.Debug("apisign debug mode connect to : http://addr" + sign_path)
			http.HandleFunc(sign_path, func(w http.ResponseWriter, r *http.Request) {
				c := &handler.Context{ResponseWriter: w, Request: r, Config: app.Config}
				resp := authenticator.DebugSignd(c)
				c.WriteResponse(resp)
				return
			})
		}
	}

	if !app.Config.GetBool("apitoken.disabled") {
		token, err := httptoken.NewTokenSigner(app.Config)
		if err != nil {
			return err
		}
		app.enable(token.Check)
	}

	if !app.Config.GetBool("apitimestamp.disabled") {
		app.enable(timestamp.GetTimestampCheck(app.Config.GetInt64("apitimestamp.no_before_sec"), app.Config.GetInt64("apitimestamp.no_after_sec")))
	}
	return nil
}

func (app *Api) Run() error {
	app.AddHandleApi(app)
	if err := app.enableGlobalMiddleware(); err != nil {
		return err
	}

	return app.run()

}

func (app *Api) initICO() {
	ico, _ := ioutil.ReadFile("app.ico")
	http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write(ico)
		return
	})
}
func (app *Api) initStatic() {
	if !app.Config.GetBool("static.enabled") {
		return
	}
	pattern := app.Config.GetString("static.pattern")
	homeDir := app.Config.GetString("static.home_dir")

	http.Handle(pattern, http.FileServer(http.Dir(homeDir)))

}

func (app *Api) HTTPRedirect() {
	if !app.Config.GetBool("redirect.enable") {
		return
	}

	httpCode := app.Config.GetInt("redirect.code")
	if httpCode == 0 {
		httpCode = 301
	}

	s := http.Server{
		Addr:    app.Config.GetString(":80"),
		Handler: NewRedirectHandler(301),
	}

	app.logger.Warnln(s.ListenAndServe())
}

func (app *Api) run() (err error) {
	app.initICO()
	app.initStatic()

	target := app.Config.GetString("server.pattern")
	http.HandleFunc(target, app.RouterHandler)

	// http redirect https
	go app.HTTPRedirect()

	s := http.Server{
		Addr:         app.Config.GetString("server.addr"),
		ReadTimeout:  time.Second * time.Duration(app.Config.GetInt("server.read_timeout")),
		WriteTimeout: time.Second * time.Duration(app.Config.GetInt("server.write_timeout")),
	}

	idleConnsClosed := make(chan struct{})
	app.sysSignal = make(chan os.Signal, 1)
	go func() {
		signal.Notify(app.sysSignal, os.Interrupt, os.Kill)
		<-app.sysSignal

		// We received an interrupt signal, shut down.
		if err := s.Shutdown(context.Background()); err != nil {
			// Error from closing listeners, or context timeout:
			fmt.Printf("HTTP server Shutdown: %v", err)

		}
		close(idleConnsClosed)
	}()

	// info action
	for appCode, handleApi := range app.handleApi {
		app.logger.Infoln("Router AppCode:", appCode)
		for action, _ := range handleApi.handles {
			app.logger.Infoln("  Action :", action)
		}
	}

	// 启动
	switch app.Config.GetString("server.schema") {
	case "https":
		if !app.Config.IsSet("server.tls_cert") {
			return fmt.Errorf("need set tls_cert")
		}
		if !app.Config.IsSet("server.tls_key") {
			return fmt.Errorf("need set tls_key")
		}

		app.logger.Infof("Listen on %s%s ", s.Addr, target)
		err = s.ListenAndServeTLS(app.Config.GetString("server.tls_cert"), app.Config.GetString("server.tls_key"))
	case "unix":
		s.Addr = app.Config.GetString("unix_socket")
		app.logger.Infof("Listen on %s%s ", s.Addr, target)
		err = s.ListenAndServe()
	default:
		app.logger.Infof("Listen on %s%s ", s.Addr, target)
		err = s.ListenAndServe()
	}

	if err != http.ErrServerClosed {
		return err
	}
	return nil
}

// func (app *Api) GetToken() *httptoken.Token {
// 	return app.token
// }

func (app *Api) AddHandleApi(handleApi *Api) error {
	if app.handleApi == nil {
		app.handleApi = make(map[string]*Api, 2)
	}

	_, has := app.handleApi[app.AppCode]
	if has {
		return fmt.Errorf("handleApi %s Is Exist", app.AppCode)
	}

	app.handleApi[handleApi.AppCode] = handleApi
	return nil
}

func (app *Api) enable(middleware ...handler.HandlerFunc) {
	if app.globalBefore == nil {
		app.globalBefore = middleware
		return
	}
	app.globalBefore = append(app.globalBefore, middleware...)
}

func (app *Api) Before(middleware ...handler.HandlerFunc) {
	if app.before == nil {
		app.before = middleware
		return
	}
	app.before = append(app.before, middleware...)
}

func (app *Api) After(middleware ...handler.HandlerFunc) {
	if app.after == nil {
		app.after = middleware
		return
	}
	app.after = append(app.after, middleware...)

}

func runMiddleware(middlewares []handler.HandlerFunc, c *handler.Context) *handler.Response {
	for _, m := range middlewares {
		resp := m(c)
		if resp != nil {
			return resp
		}
	}
	return nil
}

func (app *Api) Close() {
	select {
	case app.sysSignal <- os.Interrupt:
	case <-time.After(time.Second * 5):
	}
}

func (app *Api) Test(w http.ResponseWriter, r *http.Request) {
	app.RouterHandler(w, r)
}
